﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Drawing;
using Vortex;
using Vortex.Demo.Pacman.Units;
using System.Drawing;
using Vortex.Drawing.Particles;

namespace Vortex.Demo.Pacman {

    class WorldView : IUpdatable, IDisposable {
        
        public const int TILE_SIZE = 16;
        public const int HALF_TILE_SIZE = TILE_SIZE / 2;
        const float MOUTH_OPENED_ANGLE = 60.0f / 180.0f * (float)Math.PI;
        const float SHAKE_MINIMIZING_SPEED = 15;

        static readonly ColorU[] GHOST_COLORS = { ColorU.Red, ColorU.Pink, ColorU.Blue, ColorU.Orange };
        static readonly ColorU[] GHOST_CONFUSED_BODY_COLORS = new ColorU[] { ColorU.Navy, ColorU.Wheat };
        static readonly ColorU[] GHOST_CONFUSED_ELEMENTS_COLORS = new ColorU[] { ColorU.White, ColorU.Red };

        World m_World;

        TargetTexture m_FrameBuffer = new TargetTexture(800, 600, PixelFormat.Default);

        VertexShader m_LevelFadeShader = new VertexShader("shaders\\level-fade.vs");

        Texture m_WorldTexture;
        Texture m_PacmanFrames;
        Sprite m_PackmanPie;
        Sprite m_GhostEyes, m_GhostPupils, m_GhostConfused;
        ISpriteCollection m_wallSprites;
        ISpriteCollection m_dotsSprites;
        ISpriteCollection m_PackmanFramesSprites;

        SpriteAnimation m_GhostAnimation;
        SpriteAnimation m_PacmanDeathAnimation;

        ParticleEffect m_DeathParticleEffect;
        ParticleEffect m_EatBigParticleEffect;
        ParticleEffect m_EatSmallParticleEffect;

        ParticleSystem m_DeathParticleSystem;
        ParticleSystem m_EatBigParticleSystem;
        ParticleSystem m_EatSmallParticleSystem;

        float m_ShakeStrength;

        Random m_Random = new Random();

        public WorldView(World world) {
            m_World = world;

            ImageCollection ic = new ImageCollection();
            ic.AddImage("walls", "gfx\\walls.png");
            ic.AddImage("dots", "gfx\\dots.png");
            ic.AddImage("ghost1", "gfx\\ghost1.png");
            ic.AddImage("ghost2", "gfx\\ghost2.png");
            ic.AddImage("ghost-eyes", "gfx\\ghost-eyes.png");
            ic.AddImage("ghost-pupils", "gfx\\ghost-pupils.png");
            ic.AddImage("ghost-confused", "gfx\\ghost-confused.png");
            ic.AddImage("pacman", "gfx\\packman.png");
            ic.AddImage("death-particle", "gfx\\death-particle.png");

            m_WorldTexture = new Texture(ic, PixelFormat.DefaultAlpha);

            m_wallSprites = m_WorldTexture["walls"].Split(4, 4);
            m_dotsSprites = m_WorldTexture["dots"].Split(2, 1);

            m_GhostAnimation = new SpriteAnimation(
                new SpriteList(
                    m_WorldTexture["ghost1"],
                    m_WorldTexture["ghost2"]
                ),
                15
            );
            
            //load pacman textures
            m_PackmanPie = m_WorldTexture["pacman"];
            
            //load ghost textures
            m_GhostEyes = m_WorldTexture["ghost-eyes"];
            m_GhostPupils = m_WorldTexture["ghost-pupils"];
            m_GhostConfused = m_WorldTexture["ghost-confused"];

            m_PacmanFrames = CreatePacmanFramesTexture();
            m_PackmanFramesSprites = m_PacmanFrames.ToSprite().Split(4, 4);

            m_DeathParticleEffect = new ParticleEffect("particles\\pacman-death.peff", m_WorldTexture["death-particle"]);
            m_EatBigParticleEffect = new ParticleEffect("particles\\pacman-bigpoint.peff", m_WorldTexture["death-particle"]);
            m_EatSmallParticleEffect = new ParticleEffect("particles\\pacman-smallpoint.peff", m_WorldTexture["death-particle"]);

            m_DeathParticleSystem = m_DeathParticleEffect.CreateParticleSystem();
            m_EatBigParticleSystem = m_EatBigParticleEffect.CreateParticleSystem();
            m_EatSmallParticleSystem = m_EatSmallParticleEffect.CreateParticleSystem();

            m_DeathParticleSystem.Continous = false;
            m_EatBigParticleSystem.Continous = false;
            m_EatSmallParticleSystem.Continous = false;
        }

        private Texture CreatePacmanFramesTexture() {
            Bitmap bitmap = new Bitmap(128, 128, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            Graphics gfx = Graphics.FromImage(bitmap);
            gfx.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

            float angleStep = 180f / 16f;
            for (int n = 0; n < 4; ++n ) {
                for (int m = 0; m < 4; ++m) {
                    int offsetX = m * 32, offsetY = n * 32;
                    float currentAngle = angleStep * (n * 4 + m);
                    float sweepAngle = 360f - currentAngle * 2f;
                    gfx.FillPie(new SolidBrush(Color.White), new Rectangle(offsetX + 3, offsetY + 3, 26, 26), -currentAngle, -sweepAngle);
                }
            }

            gfx.Dispose();

            Texture texture = new Texture(bitmap);
            bitmap.Dispose();
            return texture;
        }

        #region IUpdatable Members

        public void Update(float timeDelta) {
            m_GhostAnimation.Update(timeDelta);

            m_DeathParticleSystem.Update(timeDelta);
            m_EatBigParticleSystem.Update(timeDelta);
            m_EatSmallParticleSystem.Update(timeDelta);

            if (null != m_PacmanDeathAnimation) {
                m_PacmanDeathAnimation.Update(timeDelta);
            }

            if (m_ShakeStrength > 0) {
                m_ShakeStrength -= SHAKE_MINIMIZING_SPEED * timeDelta;        
            }
        }

        #endregion

        public void Render(Canvas2D canvas) {
            float worldOffsetX = (Game.Window.Width - m_World.Map.Width * TILE_SIZE) / 2, worldOffsetY = (Game.Window.Height - m_World.Map.Height * TILE_SIZE) / 2;
           
            using (canvas <= m_FrameBuffer) {
                canvas.Clear(ColorU.Black);
                using (canvas <= new Translation(worldOffsetX, worldOffsetY)) {
                        //render everything with map offset
                        RenderMap(canvas, m_World.Map);

                        //m_ParticleSystem.Draw(canvas);
                        using (canvas <= Blending.Add) {
                            m_DeathParticleSystem.Draw(canvas);
                            m_EatBigParticleSystem.Draw(canvas);
                            m_EatSmallParticleSystem.Draw(canvas);
                        }

                        //choose pacman display rules
                        if (m_World.State.Value != WorldState.GameOver) {
                            if (m_World.State.Value == WorldState.PacmanDied) {
                                DrawPackmanDeathAnimation(canvas, m_World.Player);    
                            }
                            else {
                                DrawPackman(canvas, m_World.Player);
                            }
                        }

                        foreach (GhostUnit ghost in m_World.Ghosts) {
                            DrawGhost(canvas, ghost);
                        }
                        if (m_World.ExplicitScore != null) {
                            DrawExplicitScore(canvas, m_World.ExplicitScore);
                        }
                        DrawExtras(canvas, m_World);
                }
            }

            Vector2 offset = Vector2.Zero;
            if (m_ShakeStrength > 0) {
                offset.X = (float)(m_Random.NextDouble() - 0.5) * m_ShakeStrength;
                offset.Y = (float)(m_Random.NextDouble() - 0.5) * m_ShakeStrength;
            }

            float progress = 1;
            if (m_World.State.Value == WorldState.LevelNumber) {
                progress = m_World.State.Progress;
            }
            else if (m_World.State.Value == WorldState.LevelComplete) {
                progress = 1.0f - m_World.State.Progress;
            }

            if (m_World.State.Value == WorldState.LevelNumber || m_World.State.Value == WorldState.LevelComplete) {
                ShaderContext scope = canvas <= m_LevelFadeShader;
                scope.SetValue("distortAmount", (1 - progress) * 10);
                float phase = Game.Time.TotalGameTime * 8;
                scope.SetValue("phase", phase);
                canvas.DrawTessellatedSprite(canvas.Region + offset, m_FrameBuffer.ToSprite(), ColorU.White.SemiTransparent(progress), 70, 70);
                scope.SetValue("phase", phase + 1.158f);
                canvas.DrawTessellatedSprite(canvas.Region + offset, m_FrameBuffer.ToSprite(), ColorU.White.SemiTransparent(progress), 70, 70);
            } else {
                canvas.DrawSprite(canvas.Region + offset, m_FrameBuffer.ToSprite(), ColorU.White.SemiTransparent(progress));
            }
            
            canvas.SetVertexShader(VertexShader.Default);

            DrawWorldEvents(canvas, m_World);
        }

        private void DrawWorldEvents(Canvas2D canvas, World m_World) {
            //draw level number...
            if (m_World.State.Value == WorldState.LevelNumber) {
                string text = "LEVEL " + m_World.Level;
                Vector2 textSize = canvas.MeasureString(Assets.Instance.BigFont, text);
                canvas.DrawString(Assets.Instance.BigFont, (canvas.Size - textSize) / 2, text, ColorU.Yellow);
            }

            if (m_World.State.Value == WorldState.LevelComplete) {
                string text = "LEVEL COMPLETE!";
                Vector2 textSize = canvas.MeasureString(Assets.Instance.BigFont, text);
                canvas.DrawString(Assets.Instance.BigFont, (canvas.Size - textSize) / 2, text, ColorU.Yellow);
            }

            //draw game over text...
            if (m_World.State.Value == WorldState.GameOver) {
                string text = "GAME OVER";
                Vector2 textSize = canvas.MeasureString(Assets.Instance.BigFont, text);
                canvas.DrawString(Assets.Instance.BigFont, (canvas.Size - textSize) / 2, text, ColorU.Yellow);
            }
        }

        private void DrawExplicitScore(Canvas2D canvas, ExplicitScore explicitScore) {
            Vector2 point = (explicitScore.Location * TILE_SIZE) - new Vector2(8, 8);
            canvas.DrawString(Assets.Instance.PacmanFontSmall, point, "" + explicitScore.Score, ColorU.Wheat);
        }

        private void DrawExtras(Canvas2D canvas, World m_World) {
            float extrasHeight = (m_World.Map.Height * TILE_SIZE) - HALF_TILE_SIZE;
            canvas.DrawString(Assets.Instance.PacmanFont, new Vector2(0, extrasHeight), "Score " + m_World.Scores, ColorU.White);

            if (m_World.LifeCount > 0) {
                string text = "Lifes " + m_World.LifeCount;
                Vector2 textSize = canvas.MeasureString(Assets.Instance.PacmanFont, text);
                canvas.DrawString(Assets.Instance.PacmanFont, new Vector2((m_World.Map.Width * TILE_SIZE - HALF_TILE_SIZE) - textSize.X, extrasHeight), text, ColorU.White);
            }            
        }

        private void RenderMap(Canvas2D canvas, WorldMap worldMap) {
            ColorU wallsColor = new ColorU(0xFF0000F0);
            
            int width = worldMap.Width, height = worldMap.Height;
            float superDotPulse = (Game.Time.TotalGameTime * 2.0f) % 1.0f * 0.5f;

            //We know: this part can be easily optimized: 1) 2-pass render for walls/dot; 2) put walls and dots on the same texture
            for (int n = 0; n < height; ++n) {
                for (int m = 0; m < width; ++m ) {
                    float drawX = (float)(m * TILE_SIZE), drawY = (float)(n * TILE_SIZE);
                    WorldMapItem item = worldMap[m, n];
                    switch (item.type) {
                        case MapItemType.Dot:
                            canvas.DrawSprite(drawX, drawY, m_dotsSprites[0], ColorU.Default);
                            break;
                        case MapItemType.SuperDot:
                            canvas.DrawSprite(new Vector2(drawX, drawY), m_dotsSprites[1].Size * (1.0f + superDotPulse), superDotPulse, m_dotsSprites[1], new ColorU(ColorU.Default, 0.75f));
                            canvas.DrawSprite(drawX, drawY, m_dotsSprites[1], ColorU.Default);                            
                            break;
                        case MapItemType.Wall:
                            //canvas.DrawSprite(drawX, drawY, m_wallSprites[0], ColorU.Make(64, 255, 255, 255));
                            if (item.index > 0) {
                                canvas.DrawSprite(drawX, drawY, m_wallSprites[item.index], wallsColor);
                            }
                            break;
                    }
                }
            }
        }

        private void DrawPackman(Canvas2D canvas, PlayerUnit unit) {
            Vector2 pos = unit.Location * TILE_SIZE;
            float directionAngle = unit.DirectionAngle;
            canvas.DrawSprite(pos, directionAngle - MOUTH_OPENED_ANGLE * unit.MouthPhase, m_PackmanPie, ColorU.White);
            canvas.DrawSprite(pos, directionAngle + MOUTH_OPENED_ANGLE * unit.MouthPhase + (float)Math.PI, m_PackmanPie, ColorU.White);
        }

        private void DrawPackmanDeathAnimation(Canvas2D canvas, PlayerUnit unit) {
            if (null != m_PacmanDeathAnimation) {
                Vector2 pos = unit.Location * TILE_SIZE;
                canvas.DrawSprite(pos, -3.14152f / 2, m_PacmanDeathAnimation.ToSprite(), ColorU.Yellow);
            }
        }

        private void DrawGhost(Canvas2D canvas, GhostUnit unit) {
            Vector2 pos = unit.Location * TILE_SIZE;

            //let calculate blink phase for confused ghost
            int confusionBlinkPhase = (unit.ConfusionRatio < Constants.GHOST_CONFUSE_WARNING_TIME) ?
                (((unit.ConfusionRatio * 1.5f) % 1.0f > 0.5f) ? 0 : 1) : 0;

            if (!unit.Escaping) {
                ColorU color = unit.Confused ?
                    new ColorU(GHOST_CONFUSED_BODY_COLORS[confusionBlinkPhase], 0.75f) :
                    new ColorU(GHOST_COLORS[unit.Index], 0.75f);
                //canvas.DrawSprite(pos, unit.Phase > 0.5f ? m_Ghost1.ToSprite() : m_Ghost2.ToSprite(), color);
                canvas.DrawSprite(pos, m_GhostAnimation.ToSprite(), color);
            }

            //draw elements over the body
            if (unit.Confused) {
                canvas.DrawSprite(pos, m_GhostConfused, GHOST_CONFUSED_ELEMENTS_COLORS[confusionBlinkPhase]);
            }
            else {
                canvas.DrawSprite(pos + unit.Direction.ToVector(), m_GhostEyes, ColorU.White);
                canvas.DrawSprite(pos + unit.Direction.ToVector() * 3, m_GhostPupils, ColorU.White);
            }

            /*
            //draw location path if present
            if (unit.LocationPath != null) {
                foreach(Point point in unit.LocationPath) {
                    canvas.DrawSprite(new Vector2(point) * TILE_SIZE - 4.0f, m_Ghost1.ToSprite(), GHOST_COLORS[unit.Index].SemiTransparent(0.25f));
                }
            }
            */
        }

        #region IDisposable Members

        public void Dispose() {
            m_PacmanFrames.Dispose();
            m_WorldTexture.Dispose();
        }

        #endregion

        public void StartPacmanDeathAnimation() {
            m_PacmanDeathAnimation = new SpriteAnimation(m_PackmanFramesSprites, 16, true);
            m_PacmanDeathAnimation.CurrentFrame = 3;
            m_PacmanDeathAnimation.AnimationComplete += new AnimationCompleteEventHandler(this.PacmanDeathAnimationComplete);
        }

        private void PacmanDeathAnimationComplete(SpriteAnimation animation) {
            m_PacmanDeathAnimation = null;
            m_DeathParticleSystem.MoveTo(m_World.Player.Location * TILE_SIZE);
            m_DeathParticleSystem.Emit(200);
            m_ShakeStrength = 15;
        }

        public void ExplodeWithBigPoint() {
            m_EatBigParticleSystem.MoveTo(m_World.Player.Location * TILE_SIZE);
            m_EatBigParticleSystem.Emit(100);
            m_ShakeStrength = 10;
        }

        public void ExplodeWithSmallPoint() {
            m_EatSmallParticleSystem.MoveTo(m_World.Player.Location * TILE_SIZE);
            m_EatSmallParticleSystem.Emit(50);
        }
    }
}
